package com.hong.qrpc.selector;

import com.hong.qrpc.Peer;
import com.hong.qrpc.TransportClient;
import com.hong.qrpc.TransportSelector;
import com.hong.qrpc.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 将公共的部分提取出来成为抽象类的非抽象方法，把选择的具体算法延迟到子类中实现
 *
 * @Author HQT
 * @Date 2022/8/13 9:32
 */
@Slf4j
public abstract class AbstractTransportSelector implements TransportSelector {
    //TODO 已经做了同步效率上的优化，暂时没有测出问题
    /**
     * 已经连接好的client
     */
    protected volatile ArrayList<TransportClient> clients = new ArrayList<>();
    /**
     * 用于判断某个服务器的信息是否已经加载
     */
    protected HashMap<Peer, TransportClient> distinct = new HashMap<>();
    /**
     * 由于client的管理是读多写少，所以用读写锁的效率较高
     */
    protected ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    protected Lock readLock = readWriteLock.readLock();
    protected Lock writeLock = readWriteLock.writeLock();

    @Override
    public void init(List<Peer> peers, int count, Class<? extends TransportClient> clazz) {
        writeLock.lock();
        try {
            ArrayList<TransportClient> servers = new ArrayList<>();
            for (Peer peer : peers) {
                if (!distinct.containsKey(peer)) {
                    TransportClient client = ReflectionUtils.newInstance(clazz);
                    //需要等待连接处理完毕
                    synchronized (peer) {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                client.connect(peer);
                            }
                        }).start();
                        try {
                            peer.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    servers.add(client);
                    distinct.put(peer, client);
                    log.info("connect server : {}", peer);
                }
            }
            clients = servers;
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void release(TransportClient client) {
        writeLock.lock();
        try {
            clients.remove(client);
            //TODO 此处的O(n)有待处理为O(1)
            Peer helpRemoveKey = null;
            for (Map.Entry<Peer, TransportClient> entry : distinct.entrySet()) {
                if (entry.getValue().equals(client)) {
                    helpRemoveKey = entry.getKey();
                }
            }
            distinct.remove(helpRemoveKey);
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void close() {
        writeLock.lock();
        try {
            for (TransportClient transportClient : clients) {
                transportClient.close();
            }
            clients.clear();
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void appendTransportClient(TransportClient client, Peer helpDistinct) {
        writeLock.lock();
        try {
            if (!distinct.containsKey(helpDistinct)) {
                synchronized (helpDistinct) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            client.connect(helpDistinct);
                        }
                    }).start();
                    try {
                        helpDistinct.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                clients.add(client);
            }
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void updateTransportClients(List<TransportClient> clients, List<Peer> helpDistinct) {
        //上写锁，先使用helpDistinct去remove掉distinct里的peer，这样distinct里面剩下的就是需要移除掉的客户端，没有remove成功
        //的就代表这是新增的服务器，同时建立一个新的集合来保存change完成之后的服务器列表，以替换原来的distinct集合
        writeLock.lock();
        try {
            HashMap<Peer, TransportClient> newDistinct = new HashMap<>();
            for (int i = 0; i < clients.size(); i++) {
                Peer changedPeer = helpDistinct.get(i);
                if (distinct.containsKey(changedPeer)) {
                    //包含的话就说明该服务端没有变化
                    newDistinct.put(changedPeer, distinct.remove(changedPeer));
                } else {
                    //反之则说明这是新增的服务端
                    TransportClient newClient = clients.get(i);
                    synchronized (changedPeer) {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                newClient.connect(changedPeer);
                            }
                        }).start();
                        try {
                            changedPeer.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    this.clients.add(newClient);
                    log.info("connecting new client{}", changedPeer);
                    newDistinct.put(changedPeer, newClient);
                }
            }
            //TODO 此处的删除效率太低了，需要可以通过peer快速检测其是否已经连接，可以通过索引快速随机选择客户端，可以快速删除和添加客户端的数据结构
            //不过考虑到删除客户端的操作较少发生，所以可以接受
            //还留在distinct集合中的都是需要被排除的客户端(通过peer进行逐个比较删除)
            for (Peer peer : this.distinct.keySet()) {
                log.info("closing client{}", peer);
                this.distinct.get(peer).close();
                this.clients.remove(this.distinct.get(peer));
            }
            this.distinct = newDistinct;
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 模板方法模式
     *
     * @return
     */
    @Override
    public TransportClient select() {
        readLock.lock();
        try {
            if (clients.isEmpty()) {
                throw new IllegalStateException("cannot find legal service provider");
            }
            return select(clients);
        } finally {
            readLock.unlock();
        }
    }

    protected abstract TransportClient select(ArrayList<TransportClient> clients);
}
